/**
 * Software License, Version 1.0
 * 
 * Copyright 2003 The Trustees of Indiana University.  All rights reserved.
 * 
 *
 *Redistribution and use in source and binary forms, with or without 
 *modification, are permitted provided that the following conditions are met:
 *
 *1) All redistributions of source code must retain the above copyright notice,
 * the list of authors in the original source code, this list of conditions and
 * the disclaimer listed in this license;
 *2) All redistributions in binary form must reproduce the above copyright 
 * notice, this list of conditions and the disclaimer listed in this license in
 * the documentation and/or other materials provided with the distribution;
 *3) Any documentation included with all redistributions must include the 
 * following acknowledgement:
 *
 *"This product includes software developed by the Community Grids Lab. For 
 * further information contact the Community Grids Lab at 
 * http://communitygrids.iu.edu/."
 *
 * Alternatively, this acknowledgement may appear in the software itself, and 
 * wherever such third-party acknowledgments normally appear.
 * 
 *4) The name Indiana University or Community Grids Lab or NaradaBrokering, 
 * shall not be used to endorse or promote products derived from this software 
 * without prior written permission from Indiana University.  For written 
 * permission, please contact the Advanced Research and Technology Institute 
 * ("ARTI") at 351 West 10th Street, Indianapolis, Indiana 46202.
 *5) Products derived from this software may not be called NaradaBrokering, 
 * nor may Indiana University or Community Grids Lab or NaradaBrokering appear
 * in their name, without prior written permission of ARTI.
 * 
 *
 * Indiana University provides no reassurances that the source code provided 
 * does not infringe the patent or any other intellectual property rights of 
 * any other entity.  Indiana University disclaims any liability to any 
 * recipient for claims brought by any other entity based on infringement of 
 * intellectual property rights or otherwise.  
 *
 *LICENSEE UNDERSTANDS THAT SOFTWARE IS PROVIDED "AS IS" FOR WHICH NO 
 *WARRANTIES AS TO CAPABILITIES OR ACCURACY ARE MADE. INDIANA UNIVERSITY GIVES
 *NO WARRANTIES AND MAKES NO REPRESENTATION THAT SOFTWARE IS FREE OF 
 *INFRINGEMENT OF THIRD PARTY PATENT, COPYRIGHT, OR OTHER PROPRIETARY RIGHTS. 
 *INDIANA UNIVERSITY MAKES NO WARRANTIES THAT SOFTWARE IS FREE FROM "BUGS", 
 *"VIRUSES", "TROJAN HORSES", "TRAP DOORS", "WORMS", OR OTHER HARMFUL CODE.  
 *LICENSEE ASSUMES THE ENTIRE RISK AS TO THE PERFORMANCE OF SOFTWARE AND/OR 
 *ASSOCIATED MATERIALS, AND TO THE PERFORMANCE AND VALIDITY OF INFORMATION 
 *GENERATED USING SOFTWARE.
 */
package cgl.narada.test;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Properties;

import javax.net.ssl.HandshakeCompletedEvent;
import javax.net.ssl.HandshakeCompletedListener;
import javax.net.ssl.SSLServerSocket;
import javax.net.ssl.SSLServerSocketFactory;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;

import cgl.narada.test.transport.ssl.SSLTestBase;

/**
 * Test utility to test:
 * (1) Tunneling functionality across an HTTPS proxy or SSL enabled firewalls
 * (2) Test performance of SSL vs. fake SSL (no encryption) tunneling methods.
 *
 * This test utilizes the JSSE library to create a connection between a server
 * and a client across a HTTPS firewall.  After connection is complete, the client
 * will send the server a message "hello" and the server will reply with "world".
 * The latency is recorded and outputted as round trip latency.  Next a transfer
 * test will be conducted and the result will be printed to standard out.
 *
 * This test case was used to develop the SSL performance characteristics in
 * the Design Specification document (Specificationv2.doc).
 *
 * To run the program in server mode, execute:
 * >
 *
 * @author  John
 * @version 1.0
 */

public class HTTPSTestClient extends SSLTestBase {

    private static String s_proxyHost = null;
    private static int    s_proxyPort = 8080;
    private static boolean s_secure   = false;

    private static long    s_acceptTime    = 0;
    private static long    s_handshakeTime = 0;
    private static long    s_transferTime  = 0;
    private static byte[]  s_payload       = new byte[5000000];

    /**
     * ---------------------------------------------------------------------
     */
    public static void main(String[] argv) throws Exception {
        if (argv.length <= 0) {
            useage();
        }

        Properties sp = initializeTest(argv);

        /**
         * Comment these out to remove the use of an HTTPS Proxy.
         */
        s_proxyHost = sp.getProperty("https.proxyHost");
        s_proxyPort = Integer.parseInt(sp.getProperty("https.proxyPort"));

        String val;
        val = sp.getProperty("truststore");
        if (val != null) System.getProperties().put("javax.net.ssl.trustStore",val);
        val = sp.getProperty("truststorePassword");
        if (val != null) System.getProperties().put("javax.net.ssl.trustStorePassword",val);
        val = sp.getProperty("keystore");
        if (val != null) System.getProperties().put("javax.net.ssl.keyStore",val);
        val = sp.getProperty("keystorePassword");
        if (val != null) System.getProperties().put("javax.net.ssl.keyStorePassword",val);

        if (argv[0].equals("-client")) {
            System.out.println("=== Starting client");
            goClient(sp.getProperty("host"),Integer.parseInt(sp.getProperty("port")));
        } else {
            goServer(443);
        }

    }

    /**
     * ---------------------------------------------------------------------
     */
    public static void useage() {
        System.out.println("Useage: < \"client\" | \"server\" > ");
        System.exit(0);
    }

    /**
     * ---------------------------------------------------------------------
     */
    public static void goServer(int port) throws IOException {
        ServerSocket ss = null;

        if (!s_secure) {
            ss = new ServerSocket(port);
        } else {
            SSLServerSocketFactory sslSrvFact =
                (SSLServerSocketFactory)
                SSLServerSocketFactory.getDefault();
            ss =(SSLServerSocket)sslSrvFact.createServerSocket(port);
            System.out.println("SSL Server socket created on port "+port);
        }

        while(true) {
            try {
                Socket s = ss.accept();
                System.out.println("Accepted socket");
                s_acceptTime = System.currentTimeMillis();

                s.setSoLinger(false,0);
                s.setTcpNoDelay(true);
                InputStream input = s.getInputStream();

                byte[] hello = new byte[5];
                input.read(hello);
                byte[] world = "World".getBytes();
                s.getOutputStream().write(world);
                s.getOutputStream().flush();

                int remainingSize = s_payload.length;
                int size = 0;
                byte[] buffer = new byte[10000];
                while((size = input.read(buffer,0,10000)) < remainingSize) {
                    if (size < 0) break;
                    remainingSize -= size;
                    //System.out.println(": Read "+size+" bytes with "+remainingSize+" remaining");
                }

                long diff = System.currentTimeMillis() - s_acceptTime;
                System.out.println("=== Took "+diff+" ms to read "+s_payload.length+" bytes====");
                System.out.println("|  "+(float)s_payload.length/diff*1000+" bytes/second");
                System.out.println("=================================================");
            } catch (IOException e) {
            }
        }
    }

    /**
     * -----------------------------------------------------------------------
     */
    private static SSLSocket setupSSL(Socket tunnel,String host,int port) throws IOException {
        long starttime = System.currentTimeMillis();
        SSLSocketFactory factory =
                  (SSLSocketFactory)SSLSocketFactory.getDefault();

        /*
         * Ok, let's overlay the tunnel socket with SSL.
         */
         SSLSocket socket =
               (SSLSocket)factory.createSocket(tunnel, host, port, true);
        long diff = System.currentTimeMillis() - starttime;
        System.out.println("=====Took "+diff+" ms to create SSL Socket");

         /*
         * register a callback for handshaking completion event
         */
         socket.addHandshakeCompletedListener(
            new HandshakeCompletedListener() {
               public void handshakeCompleted(
                  HandshakeCompletedEvent event) {
                  System.out.println("Handshake finished: "+(System.currentTimeMillis() - s_handshakeTime)+" ms");
                  System.out.println(
                  "\t CipherSuite:" + event.getCipherSuite());
                  System.out.println(
                  "\t SessionId " + event.getSession());
                  System.out.println(
                  "\t PeerHost " + event.getSession().getPeerHost());
               }
            }
         );

         s_handshakeTime = System.currentTimeMillis();
         socket.startHandshake();

         return socket;
    }

    /**
     * -----------------------------------------------------------------------
     */
    public static void goClient(String host,int port) throws IOException {
         SSLSocketFactory factory =
                          (SSLSocketFactory)SSLSocketFactory.getDefault();

         System.out.println("=== SSL Socket Factory Created");

         /*
         * Set up a socket to do tunneling through the proxy.
         * Start it off as a regular socket, then layer SSL
         * over the top of it.
         */

         Socket tunnel = null;
         if (s_proxyHost != null) {
             tunnel = new Socket(s_proxyHost, s_proxyPort);
             System.out.println("=== Connected to proxy @: "+s_proxyHost+":"+s_proxyPort);
         } else {
             tunnel = new Socket(host,port);
             System.out.println("=== Connected to server @: "+host+":"+port);
         }

         try {
             if (s_proxyHost != null) {
                 doTunnelHandshake(tunnel, host, port);
             }
             tunnel.setSoLinger(false,0);
             tunnel.setTcpNoDelay(true);

             if (s_secure) {
                tunnel = setupSSL(tunnel,host,port);
                System.out.println("=== Setup SSL to: "+host+":"+port);
             }

             doTest(tunnel);
             tunnel.close();
         } catch (IOException e) {
             System.err.println("Unable to establish tunnel: ");
             e.printStackTrace();
         }
    }

    /**
     * ---------------------------------------------------------------------
     */
    private static void doTest(Socket socket) throws IOException {
        long starttime = System.currentTimeMillis();

        OutputStream out = socket.getOutputStream();
        InputStream  in  = socket.getInputStream();

        out.write("Hello".getBytes());
        byte[] world = new byte["World".length()];
        in.read(world);

        System.out.println("=== Round Trip Latency: "+(System.currentTimeMillis() - starttime)+" ms");

        for(int i=0;i < s_payload.length;i++) {
            s_payload[i] = (byte) (i % 255);
        }

        out.write(s_payload);
        out.flush();

        try { Thread.currentThread().sleep(2000); } catch (Exception e) {}
    }

    /**
     * ---------------------------------------------------------------------
     */
    private static void doTunnelHandshake(Socket tunnel, String host, int port)
                                throws IOException
    {
       OutputStream out = tunnel.getOutputStream();
       String msg = "CONNECT " + host + ":" + port + " HTTP/1.0\n"
                  + "User-Agent: SSLTest"
                  + "\r\n\r\n";
        byte b[];
        try {
             /*
             * We really do want ASCII7 -- the http protocol doesn't change
             * with locale.
             */
             b = msg.getBytes("ASCII7");
        } catch (UnsupportedEncodingException ignored) {
            /*
            * If ASCII7 isn't there, something serious is wrong, but
            * Paranoia Is Good (tm)
            */
            b = msg.getBytes();
        }
     out.write(b);
     out.flush();

      /*
      * We need to store the reply so we can create a detailed
      * error message to the user.
      */
     byte           reply[] = new byte[200];
     int            replyLen = 0;
     int            newlinesSeen = 0;
     boolean        headerDone = false;     /* Done on first newline */

     InputStream    in = tunnel.getInputStream();
     boolean        error = false;

     while (newlinesSeen < 2) {
         int i = in.read();
        if (i < 0) {
           throw new IOException("Unexpected EOF from proxy");
        }
        if (i == '\n') {
            headerDone = true;
           ++newlinesSeen;
        } else if (i != '\r') {
            newlinesSeen = 0;
            if (!headerDone && replyLen < reply.length) {
               reply[replyLen++] = (byte) i;
           }
        }
      }

     /*
      * Converting the byte array to a string is slightly wasteful
      * in the case where the connection was successful, but it's
     * insignificant compared to the network overhead.
      */
      String replyStr;
      try {
         replyStr = new String(reply, 0, replyLen, "ASCII7");
      } catch (UnsupportedEncodingException ignored) {
         replyStr = new String(reply, 0, replyLen);
      }

     /* We check for Connection Established because our proxy returns
       * HTTP/1.1 instead of 1.0 */
      //if (!replyStr.startsWith("HTTP/1.0 200")) {
     if(replyStr.toLowerCase().indexOf(
                                    "200 connection established") == -1){
         throw new IOException("Unable to tunnel through .  Proxy returns \"" +
                               replyStr + "\"");
      }

      System.out.println("Tunnel proxy connection successful");
      /* tunneling Handshake was successful! */
   }
}
